/**
 * @file
 *
 * @ingroup arm_csb336
 *
 * @brief Helper functions for SMSC LAN91C11x
 */

/*
 *  Helper functions for SMSC LAN91C11x
 *
 *  Copyright (c) 2004 by Cogent Computer Systems
 *  Written by Jay Monkman <jtm@lopingdog.com>
 *
 *  The license and distribution terms for this file may be
 *  found in the file LICENSE in this distribution or at
 *  http://www.rtems.org/license/LICENSE.
 */

#define __INSIDE_RTEMS_BSD_TCPIP_STACK__

#include <rtems.h>
#include "lan91c11x.h"

uint16_t lan91c11x_read_reg(int reg)
{
    volatile uint16_t *ptr = (uint16_t *)LAN91C11X_BASE_ADDR;
    uint16_t old_bank;
    uint16_t val;
    rtems_interrupt_level level;

    rtems_interrupt_disable(level);

    /* save the bank register */
    old_bank = ptr[7] & 0x7;

    /* set the bank register */
    ptr[7] = (reg >> 4) & 0x7;

    val = ptr[((reg & 0xf) >> 1)];

    /* restore the bank register */
    ptr[7] = old_bank;

    rtems_interrupt_enable(level);
    return val;
}

void lan91c11x_write_reg(int reg, uint16_t value)
{
    volatile uint16_t *ptr = (uint16_t *)LAN91C11X_BASE_ADDR;
    uint16_t old_bank;
    rtems_interrupt_level level;

    rtems_interrupt_disable(level);

    /* save the bank register */
    old_bank = ptr[7] & 0x7;

    /* set the bank register */
    ptr[7] = (reg >> 4) & 0x7;

    ptr[((reg & 0xf) >> 1)] = value;

    /* restore the bank register */
    ptr[7] = old_bank;

    rtems_interrupt_enable(level);
}

uint16_t lan91c11x_read_reg_fast(int reg)
{
    volatile uint16_t *ptr = (uint16_t *)LAN91C11X_BASE_ADDR;
    uint16_t val;

    val = ptr[((reg & 0xf) >> 1)];

    return val;
}

void lan91c11x_write_reg_fast(int reg, uint16_t value)
{
    volatile uint16_t *ptr = (uint16_t *)LAN91C11X_BASE_ADDR;

    ptr[((reg & 0xf) >> 1)] = value;
}


uint16_t lan91c11x_read_phy_reg(int reg)
{
    int i;
    uint16_t mask;
    uint16_t bits[64];
    int clk_idx = 0;
    int input_idx = 0;
    uint16_t phydata;

    /* 32 consecutive ones on MDO to establish sync */
    for (i = 0; i < 32; ++i) {
        bits[clk_idx++] = LAN91C11X_MGMT_MDOE | LAN91C11X_MGMT_MDO;
    }

    /* Start code <01> */
    bits[clk_idx++] = LAN91C11X_MGMT_MDOE;
    bits[clk_idx++] = LAN91C11X_MGMT_MDOE | LAN91C11X_MGMT_MDO;

    /* Read command <10> */
    bits[clk_idx++] = LAN91C11X_MGMT_MDOE | LAN91C11X_MGMT_MDO;
    bits[clk_idx++] = LAN91C11X_MGMT_MDOE;

    /* Output the PHY address, msb first - Internal PHY is address 0 */
    for (i = 0; i < 5; ++i) {
        bits[clk_idx++] = LAN91C11X_MGMT_MDOE;
    }

    /* Output the phy register number, msb first */
    mask = 0x10;
    for (i = 0; i < 5; ++i) {
        if (reg & mask) {
            bits[clk_idx++] = LAN91C11X_MGMT_MDOE | LAN91C11X_MGMT_MDO;
        } else {
            bits[clk_idx++] = LAN91C11X_MGMT_MDOE;
        }


        /* Shift to next lowest bit */
        mask >>= 1;
    }

    /* 1 bit time for turnaround */
    bits[clk_idx++] = 0;

    /* Input starts at this bit time */
    input_idx = clk_idx;

    /* Will input 16 bits */
    for (i = 0; i < 16; ++i) {
        bits[clk_idx++] = 0;
    }

    /* Final clock bit */
    bits[clk_idx++] = 0;

    /* Turn off all MII Interface bits */
    lan91c11x_write_reg(LAN91C11X_MGMT,
                        lan91c11x_read_reg(LAN91C11X_MGMT) & 0xfff0);

    /* Clock all 64 cycles */
    for (i = 0; i < sizeof bits; ++i) {
        /* Clock Low - output data */
        lan91c11x_write_reg(LAN91C11X_MGMT, bits[i]);
        rtems_task_wake_after(1);

        /* Clock Hi - input data */
        lan91c11x_write_reg(LAN91C11X_MGMT, bits[i] | LAN91C11X_MGMT_MCLK);
        rtems_task_wake_after(1);
        bits[i] |= lan91c11x_read_reg(LAN91C11X_MGMT) & LAN91C11X_MGMT_MDI;
    }

    /* Return to idle state */
    /* Set clock to low, data to low, and output tristated */
    lan91c11x_write_reg(LAN91C11X_MGMT, lan91c11x_read_reg(LAN91C11X_MGMT) & 0xfff0);
    rtems_task_wake_after(1);

    /* Recover input data */
    phydata = 0;
    for (i = 0; i < 16; ++i) {
        phydata <<= 1;

        if (bits[input_idx++] & LAN91C11X_MGMT_MDI) {
            phydata |= 0x0001;
        }
    }

    return phydata;
}



void lan91c11x_write_phy_reg(int reg, uint16_t phydata)
{
    int i;
    ushort mask;
    ushort bits[64];
    int clk_idx = 0;

    /* 32 consecutive ones on MDO to establish sync */
    for (i = 0; i < 32; ++i) {
        bits[clk_idx++] = LAN91C11X_MGMT_MDOE | LAN91C11X_MGMT_MDO;
    }

    /* Start code <01> */
    bits[clk_idx++] = LAN91C11X_MGMT_MDOE;
    bits[clk_idx++] = LAN91C11X_MGMT_MDOE | LAN91C11X_MGMT_MDO;

    /* Write command <01> */
    bits[clk_idx++] = LAN91C11X_MGMT_MDOE;
    bits[clk_idx++] = LAN91C11X_MGMT_MDOE | LAN91C11X_MGMT_MDO;

    /* Output the PHY address, msb first - Internal PHY is address 0 */
    for (i = 0; i < 5; ++i) {
        bits[clk_idx++] = LAN91C11X_MGMT_MDOE;
    }

    /* Output the phy register number, msb first */
    mask = 0x10;
    for (i = 0; i < 5; ++i) {
        if (reg & mask) {
            bits[clk_idx++] = LAN91C11X_MGMT_MDOE | LAN91C11X_MGMT_MDO;
        } else {
            bits[clk_idx++] = LAN91C11X_MGMT_MDOE;
        }

        /* Shift to next lowest bit */
        mask >>= 1;
    }

    /* 2 extra bit times for turnaround */
    bits[clk_idx++] = 0;
    bits[clk_idx++] = 0;

    /* Write out 16 bits of data, msb first */
    mask = 0x8000;
    for (i = 0; i < 16; ++i) {
        if (phydata & mask) {
            bits[clk_idx++] = LAN91C11X_MGMT_MDOE | LAN91C11X_MGMT_MDO;
        } else {
            bits[clk_idx++] = LAN91C11X_MGMT_MDOE;
        }

        /* Shift to next lowest bit */
        mask >>= 1;
    }

    /* Turn off all MII Interface bits */
    lan91c11x_write_reg(LAN91C11X_MGMT,
                        lan91c11x_read_reg(LAN91C11X_MGMT) & 0xfff0);

    /* Clock all 64 cycles */
    for (i = 0; i < sizeof bits; ++i) {
        /* Clock Low - output data */
        lan91c11x_write_reg(LAN91C11X_MGMT, bits[i]);
        rtems_task_wake_after(1);

        /* Clock Hi - input data */
        lan91c11x_write_reg(LAN91C11X_MGMT, bits[i] | LAN91C11X_MGMT_MCLK);
        rtems_task_wake_after(1);
        bits[i] |= lan91c11x_read_reg(LAN91C11X_MGMT) & LAN91C11X_MGMT_MDI;
    }

    /* Return to idle state */
    /* Set clock to low, data to low, and output tristated */
    lan91c11x_write_reg(LAN91C11X_MGMT,
                        lan91c11x_read_reg(LAN91C11X_MGMT) & 0xfff0);
    rtems_task_wake_after(1);

}



